﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.ComponentModel;

namespace PDX
{
    /// <summary>
    /// Base class for a UI Element that supports conventional data templates.
    /// </summary>
    public abstract class Element : Control, IRaiseDependencyPropertyChanges
    {
        /// <summary>
        /// Occurs when an element has been constructed
        /// </summary>
        public static event EventHandler<GenericEventArgs<Element>> OnConstructed = delegate { };

        /// <summary>
        /// Initializes a new instance of the <see cref="Element"/> class.
        /// </summary>
        public Element()
        {
            this.Loaded += new System.Windows.RoutedEventHandler(Element_Loaded);
            this.DataContextChanged += new System.Windows.DependencyPropertyChangedEventHandler(Element_DataContextChanged);

            IsTabStop = false;

            OnConstructed(this, new GenericEventArgs<Element>(this));
        }

        /// <summary>
        /// Indicates that the initialization process for the element is complete.
        /// </summary>
        /// <exception cref="T:System.InvalidOperationException">
        ///   <see cref="M:System.Windows.FrameworkElement.EndInit"/> was called without <see cref="M:System.Windows.FrameworkElement.BeginInit"/> having previously been called on the element.</exception>
        public override void EndInit()
        {
            base.EndInit();

            UpdateTemplates();
        }

        void Element_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
        {
            UpdateTemplates();
        }

        bool loadedFirstTime = true;
        void Element_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (loadedFirstTime)
            {
                loadedFirstTime = false;
                UpdateTemplates();
            }
        }

        /// <summary>
        /// Updates the conventional and declared templates
        /// </summary>
        void UpdateTemplates()
        {
            UpdateConventionalTemplate();
            UpdateDeclarationTemplate();
        }

        /// <summary>
        /// Updates the conventional template.
        /// </summary>
        void UpdateConventionalTemplate()
        {
            var templateSelector = GetValue(ConventionalTemplateSelectorProperty) as DataTemplateSelector;

            if (templateSelector == null)
            {
                templateSelector = new DefaultConventionalTemplateSelector();
            }

            var template = templateSelector.SelectTemplate(DataContext, this);
            ConventionalTemplate = template;
        }

        /// <summary>
        /// Updates the declaration template.
        /// </summary>
        void UpdateDeclarationTemplate()
        {
            var declaration = Context.GetDeclaration(this);

            DataTemplate declaredTemplate = null;

            if (declaration != null)
            {
                foreach (var template in declaration.CustomTemplates)
                {
                    if((template.DataType as Type) == this.GetType())
                    {
                        declaredTemplate = template;
                        break;
                    }
                }
            }

            DeclarationTemplate = declaredTemplate;
        }




        /// <summary>
        /// Gets or sets the <see cref="ConventionalTemplateProperty"/>.
        /// </summary>
        /// <value>
        /// The conventional template.
        /// </value>
        public DataTemplate ConventionalTemplate
        {
            get { return (DataTemplate)GetValue(ConventionalTemplateProperty); }
            set { SetValue(ConventionalTemplateProperty, value); }
        }

        /// <summary>
        /// The conventional DataTemplate for this element, selected using the
        /// <see cref="ConventionalTemplateSelectorProperty"/>.
        /// </summary>
        public static readonly DependencyProperty ConventionalTemplateProperty =
            DependencyProperty.Register("ConventionalTemplate", typeof(DataTemplate),
            typeof(Element), new UIPropertyMetadata(null));





        /// <summary>
        /// Gets or sets the <see cref="DeclarationTemplateProperty"/>.
        /// </summary>
        /// <value>
        /// The declaration template.
        /// </value>
        public DataTemplate DeclarationTemplate
        {
            get { return (DataTemplate)GetValue(DeclarationTemplateProperty); }
            set { SetValue(DeclarationTemplateProperty, value); }
        }

        /// <summary>
        /// The DataTemplate for this element as declared by a <see cref="Declaration"/>.
        /// </summary>
        public static readonly DependencyProperty DeclarationTemplateProperty =
            DependencyProperty.Register("DeclarationTemplate", typeof(DataTemplate),
            typeof(Element), new UIPropertyMetadata(null));




        /// <summary>
        /// Gets or sets the <see cref="CustomTemplateProperty"/>.
        /// </summary>
        /// <value>
        /// The custom template.
        /// </value>
        public DataTemplate CustomTemplate
        {
            get { return (DataTemplate)GetValue(CustomTemplateProperty); }
            set { SetValue(CustomTemplateProperty, value); }
        }

        /// <summary>
        /// The DataTemplate for this element as defined in XAML.
        /// </summary>
        public static readonly DependencyProperty CustomTemplateProperty =
            DependencyProperty.Register("CustomTemplate", typeof(DataTemplate),
            typeof(Element), new UIPropertyMetadata(null));





        /// <summary>
        /// Gets the conventional template selector.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static DataTemplateSelector GetConventionalTemplateSelector(DependencyObject obj)
        {
            return (DataTemplateSelector)obj.GetValue(ConventionalTemplateSelectorProperty);
        }

        /// <summary>
        /// Sets the conventional template selector.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetConventionalTemplateSelector(DependencyObject obj, DataTemplateSelector value)
        {
            obj.SetValue(ConventionalTemplateSelectorProperty, value);
        }

        /// <summary>
        /// A DataTemplateSelector that selects a conventional DataTemplate
        /// for the concrete implementation of this <see cref="Element"/>.
        /// </summary>
        public static readonly DependencyProperty ConventionalTemplateSelectorProperty =
            DependencyProperty.RegisterAttached("ConventionalTemplateSelector", typeof(DataTemplateSelector), typeof(Element),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));




        /// <summary>
        /// Occurs when the value of a PDX-related DependencyProperty changes.
        /// </summary>
        public event DependencyPropertyChangedEventHandler DependencyPropertyChanged = delegate { };

        /// <summary>
        /// Raises the <see cref="DependencyPropertyChanged"/> event with the
        /// specified <see cref="System.Windows.DependencyPropertyChangedEventArgs"/>.
        /// </summary>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        public void Raise(DependencyPropertyChangedEventArgs args)
        {
            DependencyPropertyChanged(this, args);
        }

        FrameworkElement IRaiseDependencyPropertyChanges.Self
        {
            get { return this; }
        }
    }
}
